package com.jourwon.spring.boot.aspect;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Joiner;
import com.jourwon.spring.boot.util.IpUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;

/**
 * 接口请求响应日志切面
 * <p>
 * AOP通知注解执行顺序
 * 正常情况：-> @Around -> @Before -> Method -> @Around -> @After -> @AfterReturning
 * 异常情况：-> @Around -> @Before -> Method -> @Around -> @After -> @AfterThrowing
 *
 * @author JourWon
 * @date 2022/6/25
 */
@Slf4j
@Aspect
@Component
public class WebLogAspect {

    /**
     * 请求响应参数最大值
     */
    private static final Integer MAX_ARGS_SIZE = 1024;

    /**
     * ApiOperation注解为切入点
     */
    @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
    public void webLog() {
    }

    /**
     * 前置通知:在目标方法调用前执行增强方法
     *
     * @param joinPoint 连接点
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 开始打印请求日志
        log.info("==================== Start ====================");
        // 打印请求 [Http method]URL
        log.info("[HTTP Method]URL:[{}]{}", request.getMethod(), request.getRequestURL().toString());
        // 打印请求头
        Enumeration<String> headerNames = request.getHeaderNames();
        List<String> headerList = new ArrayList<>();
        while (headerNames.hasMoreElements()) {
            String nextElement = headerNames.nextElement();
            String header = request.getHeader(nextElement);
            headerList.add("[" + nextElement + ":" + header + "]");
        }
        String headers = Joiner.on(";").join(headerList);
        log.info("HTTP Headers:{}", headers);

        // 打印请求的 IP
        log.info("IP:{}", IpUtils.getIpAddr());

        // 获取 @ApiOperation 注解的描述信息
        String methodDescription = StringUtils.EMPTY;
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        ApiOperation apiOperation = methodSignature.getMethod().getAnnotation(ApiOperation.class);
        if (Objects.nonNull(apiOperation)) {
            methodDescription = apiOperation.value();
        }
        // 打印描述信息
        log.info("Description:{}", methodDescription);

        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method:{}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());

        String contentType = request.getContentType();
        // 上传文件类的请求不打印请求入参
        boolean flag = contentType != null
                && (contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE) || contentType.contains(MediaType.MULTIPART_MIXED_VALUE));
        if (flag) {
            return;
        }

        String requestArgs = JSON.toJSONString(joinPoint.getArgs());
        if (requestArgs.length() > MAX_ARGS_SIZE) {
            requestArgs = requestArgs.substring(0, MAX_ARGS_SIZE);
        }
        // 打印请求入参
        log.info("Request Args:{}", requestArgs);
    }

    /**
     * 环绕通知:包裹目标方法，在目标方法前后执行自定义代码
     *
     * @param proceedingJoinPoint 连接点
     * @return Object
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();

        String responseArgs = JSON.toJSONString(result);
        if (responseArgs.length() > MAX_ARGS_SIZE) {
            responseArgs = responseArgs.substring(0, MAX_ARGS_SIZE);
        }
        // 打印出参
        log.info("Response Args:{}", responseArgs);
        // 执行耗时
        log.info("Time Consuming:{} ms", System.currentTimeMillis() - startTime);

        log.info("==================== End ====================");
        return result;
    }

}
